package com.easyaop.core.transformer;

import com.easyaop.api.Context;
import com.easyaop.api.advisor.Advisor;
import com.easyaop.api.advisor.AroundAdvisor;
import com.easyaop.api.advisor.EventAdvisor;
import com.easyaop.api.around.annotation.*;
import com.easyaop.core.interceptor.AroundDelegation;
import com.easyaop.commons.utils.PointcutUtils;
import com.easyaop.core.interceptor.EventAdvice;
import com.easyaop.core.matcher.MethodElementMatcher;

import com.easyaop.core.matcher.TypeElementMatcher;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import java.security.ProtectionDomain;

public class PointcutTransformer implements AgentBuilder.Transformer {
    @Override
    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {

        Advisor[] advisors = Context.ADVISORS.toArray(new Advisor[0]);
        MethodList<MethodDescription.InDefinedShape> methods = typeDescription.getDeclaredMethods()
                .filter(ElementMatchers.isMethod()
                        .and(ElementMatchers.not(ElementMatchers.isAbstract()
                                .or(ElementMatchers.isNative()
                                )))
                );
        MethodElementMatcher matcherMethod = new MethodElementMatcher(typeDescription, classLoader);

        for (MethodDescription.InDefinedShape methodDescription : methods) {
            boolean isEvent = false;
            boolean isAround = false;


            for (Advisor advisor : advisors) {

                if (!TypeElementMatcher.matches(typeDescription, advisor.getPointcut())) {
                    continue;
                }

                boolean matches = matcherMethod.matches(methodDescription, advisor.getPointcut());
                if (!matches) {
                    continue;
                }

                advisor.addSignature(MethodElementMatcher.getSignature(methodDescription));
                if (advisor instanceof AroundAdvisor) {
                    isAround = true;
                } else if (advisor instanceof EventAdvisor) {
                    isEvent = true;
                }
            }
            if (isAround) {
                builder = around(builder, methodDescription);
            }
            if (isEvent) {
                builder = event(builder, methodDescription);
            }

        }
        return builder;
    }

    private DynamicType.Builder<?> event(DynamicType.Builder<?> builder, MethodDescription methodDescription) {
        return builder.visit(Advice.to(EventAdvice.class).on(methodDescription::equals));
    }

    private DynamicType.Builder<?> around(DynamicType.Builder<?> builder, MethodDescription methodDescription) {
        return builder.method(ElementMatchers.isMethod().and(methodDescription::equals))
                .intercept(MethodDelegation.withDefaultConfiguration()
                        .withBinders(CustomCallImpl.Binder.INSTANCE, CustomOriginImpl.Binder.INSTANCE,
                                CustomTargetImpl.Binder.INSTANCE)
                        .to(AroundDelegation.class));
    }

}
